package com.ssm.gateway.exception;

import cn.dev33.satoken.exception.SaTokenException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ssm.gateway.entity.Result;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.function.Supplier;

/**
 * 网关全局异常处理
 */
@Component
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
        ServerHttpRequest request = serverWebExchange.getRequest();
        ServerHttpResponse response = serverWebExchange.getResponse();

        Integer code = 200;
        String message = "";
        if(throwable instanceof SaTokenException) { //如果异常对象为SaTokenException的实现类
            code = 401;   //和登录、无权限等相关的错误返回用户无权限
            message = "用户无权限";
        } else {
            code = 500; // 限流、熔断降级等错误返回系统繁忙
            message = "系统繁忙";
        }
        Result fail = Result.fail(code, message);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON); //设置客户端返回的响应体数据格式是JSON
        return response.writeWith(Mono.fromSupplier(new Supplier<DataBuffer>() {
            @Override
            public DataBuffer get() {
                DataBufferFactory dataBufferFactory = response.bufferFactory();
                byte[] bytes = null;
                try {
                    bytes = objectMapper.writeValueAsBytes(fail);
                } catch (JsonProcessingException e) {
                    throw new RuntimeException(e);
                }
                return dataBufferFactory.wrap(bytes);
            }
        }));

        /**
         * 1.response.writeWith方法是 Spring WebFlux 中用于将给定的 Publisher（发布者，在响应式编程中用于异步提供数据）中的数据写入到服务器响应体的方法。
         * 这里传入的参数是一个 Mono，Mono 是一种特殊的 Publisher，表示它最多只发布一个元素（在这个场景下，就是包含错误信息的 DataBuffer），非常适合用于返回单个响应体这种情况。
         * 2.Mono.fromSupplier方法接受一个 Supplier 接口的实现作为参数，并基于此创建一个 Mono。Supplier 接口是一个函数式接口，它定义了一个无参的 get 方法，用于提供一个值（在这里就是要返回一个 DataBuffer 对象）。
         * 3. Supplier<DataBuffer> 代表我们要获取一个DataBuffer对象，在匿名内部类中首先要创建DataBufferFactory工厂，用于创建DataBuffer对象(DataBuffer是响应体数据)
         * 不同的实现可能对应不同的底层存储机制（比如内存、堆外内存等），而 DataBufferFactory 则提供了统一的创建方式来适配这些不同的场景。
         * 4. objectMapper是Jackson位于Jackson包下，用于 JSON 序列化和反序列化。此处将fail信息转为byte数组。
         * 5.dataBufferFactory.wrap方法是将字节数组 bytes 包装成一个 DataBuffer 对象。
         */
    }
}
